// S-38.3148 Simulation of data networks
//
// Instructions: 
// - Implement the required functions and functionality as given in the assignment instructions
// - Make the changes you feel are necessary
// - You may modify anything you want, the most important thing is to make a working simulator :)
// - Try to make as much as you can before coming to the exercise classes. The two exercise classes are
//   the primary source for help during this assignment. 
//
#include <string>
#include <iostream>
#include <vector>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <cstdio>
#include <ctime>
#include <CNCL/Uniform.h>
#include <CNCL/Job.h>
#include <CNCL/MT.h>
#include <CNCL/NegExp.h>
#include <CNCL/EventScheduler.h>
#include <CNCL/EventHandler.h>
#include <CNCL/Moments.h>

using std::vector;
using std::cout;
using std::endl;
using std::string;

enum ev_types {EV_TIME, EV_WP, EV_ARRIVAL, EV_NodeReady}; //event types

// Global constants
// - Use what you feel is necessary, or implement as arguments to main() or define in main().
const double R1 = 1.0;
const double delta_time = 0.00167;	// delta time is 1.67ms
double TransferRequestArrivalRate = 0.1; //the arrival rate of file downloads (per second)
const double R0 = 0.1;	// the distance within which a node get full service rate
const double C0 = 2457600;	// the full service rate bits/s
const double ALPHA = 2.5;	// the path loss parameter
const unsigned long NrOfTransientArrivals = 500;

double nodeMovementTransientTime = 0;
double simulationStopTime = 10000;	// when shall the simulation stops ( in seconds)

class Node;
class Controller;

// Class definition of Node
// - you may want to add some members, e.g. some CNMoments for statistics collection, implement
//   whatever extra functionality you feel convenient with.
// - if you add private members remember to initialize them in the constructor.
class Node:public CNEventHandler
{
private:
    CNRandom &rnd_xy;        //random variable for x and y parameters   
    CNRandom &rnd_v;         //random variable for speed
    double x_next;           //next destination point
    double y_next;
    double x_prev;           //previous destination point
    double y_prev;
    CNSimTime prevtime;      //time at previous destination point
    double legLength;		// the length of the leg (from prev to next)

    double travledDistance ; // the distanced travled by this node
    volatile bool steadyState;	// whether simulation in steady state
    bool simulationTerminated; // whether the simultation has stopped
    void setNextWayPoint();	// set the next WayPoint for this node
    void generateNextWP();
    string nodeID;

public:
    double x;                //current location
    double y;
    double speed;            //current speed
    CNJob* file;             //file the node is downloading (can be NULL as well)
    Controller* controller;
    CNMoments velocityCollector;	// Evaluate the velocity of nodes
    CNMoments locationCollector;
    long nrofServedFiles;
    void init();
    void location(double n);
    void add_job(CNSimTime time, CNJob * newJob);
    double del_job(CNSimTime time);
    void display_results();      // Displays collected results
    virtual void event_handler(const CNEvent *ev);
    bool isInSteadyState();	// whether the node is in steadyState
    double getServiceRate(); // calculate service rate for this node at its current location
    // Constructor 
    Node(CNRandom &rnd1, CNRandom &rnd2): rnd_xy(rnd1), rnd_v(rnd2),
        x_next(0), y_next(0), x_prev(0), y_prev(0), prevtime(0), x(0), 
        y(0), speed(0), controller(NIL), velocityCollector("velocityCollector"), locationCollector("locationCollector") {

    	travledDistance = 0; // the distanced travled by this node
        steadyState = false;	// whether simulation in steady state
        file = NULL;
        nrofServedFiles=0;

    }

    void setNodeID(string id) {
    	nodeID = id;
    }

    string getNodeID() {
    	return nodeID;
    }
};

// Class definition of the controller
// - Again you may want to add at least some statistics collection
//
class Controller:public CNEventHandler
{
private: 
    CNSimTime sim_time;             // total simulation time
    CNSimTime sample_time;          // interval between two samples, 1.67ms in our assignment
    unsigned int num_of_nodes;
    CNRandom &rnd_arr;              // Random arrival times
    CNRandom &rnd_jobsize;          // Random job sizes
    vector<Node*> idle_nodes;       // Vector of idle nodes
    vector<Node*> job_nodes;        // Vector of nodes with job
    unsigned int simulation;        // Number of running simulation, not necessarily
    unsigned long arrivals;			// number of arrivals
    unsigned long blockedRequest;	// number of blocked requests
    CNMoments sojournTimeCollector; // Evaluation of throughput

    volatile bool movementTransientPassed;	// whether all the nodes passed the initial transient status in terms of movement
    volatile bool arrivalTransientPassed;	// whether arrival process passed the transient status
    void generateNextArrival();
    void doFileTransfer();

public:
    virtual void event_handler(const CNEvent *ev);
    // Adds a node to the simulator / free list
    void add_node(Node* node)
    {
        idle_nodes.push_back(node);
        return;
    }
    // Changes nodes between free list and reserve list
    void change_list(vector<Node*> &from, vector<Node*> &to, Node* node)
    {
        vector<Node*>::iterator nodeloc = 
                find(from.begin(), from.end(), node);
        from.erase(nodeloc);
        to.push_back(node);
    } 
    double distance(Node* n1, Node* n2);
    void display_results();      // Displays collected results
    double getThroughput();	// return the calculated throughput
    double getBolckingProbability();	// return the blocking probability


    // Constructor
    Controller(CNRandom &rnd1, CNRandom &rnd2, double stime, double t, int n): 
        sim_time(stime), sample_time(t), num_of_nodes(n), rnd_arr(rnd1), 
        rnd_jobsize(rnd2), idle_nodes(NIL), job_nodes(NIL), simulation(1),
        arrivals(0), sojournTimeCollector("sojournTimeCollector"){

    	movementTransientPassed = false;
        arrivalTransientPassed = false;
        blockedRequest = 0;

    }

    vector<Node*> getIdleNodes() {
    	return idle_nodes;       // Vector of idle nodes
    }

    vector<Node*> getActiveNodes() {
    	return job_nodes;       // Vector of active(job) nodes
    }

};

// whether the node is in steadyState
bool Node::isInSteadyState() {
	return steadyState;
}

void Node::setNextWayPoint()
{
    do{
        x_next = rnd_xy();
        y_next = rnd_xy();
    } while(sqrt(y_next * y_next + x_next * x_next) > R1);
}

// Initialize a node
// 
void Node::init()
 {
	do {
		x_prev = x_next = this->rnd_xy();
		y_prev = y_next = this->rnd_xy();
	} while ((x_next * x_next + y_next * y_next) > (R1 * R1));

	return;
}

//calculates the node location based on knowledge about 
//waypoints (prev and next) and time of departure from prev
void Node::location(CNSimTime n)
{
    double s=(n-prevtime)*speed;
//    double d=sqrt((x_next-x_prev)*(x_next-x_prev)
//                   +(y_next-y_prev)*(y_next-y_prev));
    x=x_prev+s*(x_next-x_prev)/legLength;
    y=y_prev+s*(y_next-y_prev)/legLength;
    return;    
}

// Deletes a finished job, returns the time needed to 
// finish the whole job.
double Node::del_job(CNSimTime time)
{
    file->out = time; 
    double total_time = file->out - file->in; 
    delete file; 
    file = NULL;
    nrofServedFiles ++;
    return total_time; 
}

void Node::add_job(CNSimTime time, CNJob * newJob) {
	file = newJob;
	file->in = time;
//	cout << "associated new job with " << getNodeID() << endl;
}

void Node::generateNextWP()
{
    // save current location and update prevtime
    x_prev = x_next;
    y_prev = y_next;
    prevtime = now();
    // choose speed to go to next waypoint
    speed = rnd_v.draw();
    // generate next WP location
    setNextWayPoint();
    legLength = sqrt((x_next - x_prev) * (x_next - x_prev) + (y_next - y_prev) * (y_next - y_prev));
    double nextWPEventDelay = legLength / speed;
    // generate next EV_WP event, target is
    send_delay(new CNEvent(EV_WP), nextWPEventDelay);
}

// Implement the mobility for the node (PART 1):
// - Draw new destination point from uniform distribution.
// - Draw new speed from desired distribution.
// - Write current time to prevtime.
// - Node shoud receive EV_WP event when it arrives to the destination point.
// - location and distance member functions might be handy here
void Node::event_handler(const CNEvent *ev)
 {
	if (now() >= simulationStopTime) {
//		cout << "simulation stop time reached for " << nodeID << endl;
//		display_results();
		return;
	}

	switch (ev->type()) {

	case EV_WP: {
		// generate the next WP
		generateNextWP();

		if (!steadyState && now() > nodeMovementTransientTime) {
			this->steadyState = true;
			// start triggering the controller logic
			CNEvent * evtimeEvent = new CNEvent(EV_NodeReady, controller);
			send_now(evtimeEvent);
		}
	}
		break;

	default:
		error("cncl assignment : ", "illegal state");
		break;
	}
}

//calculates the distance between two nodes
double Controller::distance(Node* n1, Node* n2)
{
    double distance = sqrt((n1->x-n2->x)*(n1->x-n2->x)
                            +(n1->y-n2->y)*(n1->y-n2->y));
    return distance;
}

void Controller::generateNextArrival()
{
    // start generating new arrival event
    CNJob *newFileTransferRequest = new CNJob(rnd_jobsize.draw());
    CNEvent *newArrival = new CNEvent(EV_ARRIVAL, this, newFileTransferRequest);
    send_delay(newArrival, rnd_arr.draw());
}

// Implement the job arrival and service functionalities and data collection (PART 2):
// - The controller sends EV_TIME event to itself every t seconds,
//   where t is the sampling interval.
// - Select at random a node for service.
// - Calculate the locations of the nodes when necessary.
// - EV_ARRIVAL is sent when a new job arrives.
// - Generate a CNJob object corresponding to the arrival.
// - Check for transient and simulation stopping conditions.
// - You can use the change_list member function to change nodes between
//   free and reserve lists. 
// - You can use del_job member function (for nodes) to delete completed
//   downloads
void Controller::event_handler(const CNEvent *ev)
 {
	if (now() >= sim_time) {
		// simulation stops!
		return;
	}

	switch (ev->type()) {
	case EV_NodeReady: {
		// check if we've passed the node movement process transient period
		if (!movementTransientPassed) {
			bool movementTransientPassedForAllNodes = true;
			for (size_t i = 0; i < idle_nodes.size(); i++) {
				if (!idle_nodes[i]->isInSteadyState()) {
					movementTransientPassedForAllNodes = false;
					break;
				}
			}

			if (movementTransientPassedForAllNodes) {
				// all nodes has passed the transient movement period
				movementTransientPassed = true;
				cerr << "controller start generating arrivals" << endl;
				// start generating arrivals events
				generateNextArrival();

				// generate next EV_TIME event
				send_delay(new CNEvent(EV_TIME), delta_time);
			}
		}

	}
		break;
	case EV_TIME: {

		// randomly select an active node to do transfer
		doFileTransfer();

		// generate next EV_TIME event
		send_delay(new CNEvent(EV_TIME), delta_time);

	}
		break;

	case EV_ARRIVAL: {
		arrivals++;
//		cout << " arrivals = " << arrivals << " time = " << now() << endl;

		// check if we've passed the arrival transient period
		if (!arrivalTransientPassed && (arrivals >= NrOfTransientArrivals)) {
			arrivalTransientPassed = true;

			//reset the arrival and blocked Request count
			arrivals = 0;
			blockedRequest = 0;
//			cout << "start collecting statistics" << endl;
		}

		if (idle_nodes.empty()) {
			// if all nodes are busy, reject this arrival
			blockedRequest ++;
//			cerr << "new arrival is rejected" << endl;
		} else {
			CNJob *job;
			job = (CNJob *) ev->object();
			job->in = now();

			// randomly select an idle node and associate it with this job
			size_t candidateIdx = rand() % idle_nodes.size();
			Node * candidateNode = idle_nodes[candidateIdx];
			candidateNode->add_job(now(), job);

			this->change_list(idle_nodes, job_nodes, candidateNode);
		}

		// generate next Arrival
		generateNextArrival();

	}
		break;

	default:
		error("cncl assignment 1: ", "illegal state");
		break;
	}
}

void Controller::doFileTransfer() {
	if (job_nodes.empty()) {
		return;
	}

	//select one node and serve its file transfer
	size_t candidateIdx = rand() % job_nodes.size();
	Node * luckyNode = job_nodes[candidateIdx];

	//update this node's location
	luckyNode->location(now());

	//serve its file tranfer for this interval
	luckyNode->file->length = luckyNode->file->length - (luckyNode->getServiceRate() * delta_time);

	if (luckyNode->file->length <= 0) {
//		cerr << "job finished on node " << luckyNode->getNodeID() << " jobNodes: " << job_nodes.size() << endl;
		double sojournTime = luckyNode->del_job(now()+sample_time);
		if (this->arrivalTransientPassed) {
		    sojournTimeCollector.put(sojournTime); // Evaluation of throughput
		}

		// move this node back to idle_nodes
		change_list(job_nodes, idle_nodes, luckyNode);
	}
}

// calculate service rate for this node at its current location
double Node::getServiceRate() {

	double r = sqrt(x*x + y*y);
	if (r<=R0) {
		return C0;
	}

	return C0 *pow((R0/r), ALPHA);
}


// For display of results
// - implement what you need is necessary and is asked in the assignment.
void Controller::display_results()
{
//	cout << sojournTimeCollector << endl;
//	cout << "mean sojourn time:" << sojournTimeCollector.mean() << endl;
    cout << "throughput:" << 200000.0/sojournTimeCollector.mean() << endl;
    cout << "blocking probability: " << getBolckingProbability()<< endl;
    return;
}

double Controller::getThroughput() {
	return 200000.0/sojournTimeCollector.mean() ;
}

double Controller::getBolckingProbability() {
	return (blockedRequest * 1.0)/(arrivals);
}

// More results, not necessarily needed 
void Node::display_results()
{
}    

double calculateThroughput(double v0)
{
    time_t seconds;
    seconds = time (NULL);
    // random variable for arrival and job size
    CNRNG *rng3 = new CNMT(seconds % 32);
    CNRNG *rng4 = new CNMT(seconds % 18 + 87);
    CNNegExp rnd_arr(1 / TransferRequestArrivalRate, rng3);
    CNNegExp rnd_jobsize(200000.0, rng4);
    int numOfNodes = 20;
    // create the controller
    Controller controller(rnd_arr, rnd_jobsize, simulationStopTime, delta_time, numOfNodes);
    CNRNG *rng = new CNMT(seconds % 1000);
    //random variable for x,y location U~U(-r1, r1)
    CNUniform rnd_xy(-1.0 * R1, R1, rng);
    //random variable for speed V~U(v0,2v0)
    CNUniform rnd_v(1.0 * v0, 2.0 * v0, rng);
    // create 20 nodes and add them to controller
    for(int i = 0;i < numOfNodes;i++){
        Node *newNode = new Node(rnd_xy, rnd_v);
        stringstream oss;
        oss << "Node_" << i;
        newNode->setNodeID(oss.str());
        newNode->controller = &controller;
        newNode->init();
        controller.add_node(newNode);
    }
    cout << "start simulation" << endl;
    CNEventScheduler scheduler;
    vector<Node*> allNodes = controller.getIdleNodes();
    for(size_t i = 0;i < allNodes.size();i++){
        scheduler.send_event(new CNEvent(EV_WP, allNodes[i], 0.));
    }
    scheduler.start();
    controller.display_results();
    return controller.getThroughput();
}

void movementVelocityDependencyStudy()
{
    // prompt user for v0
    double v0 = 0;
    cout << "input v0" << endl;
    cin >> v0;
    cout << "v0 is " << v0 << endl;
    nodeMovementTransientTime = 4.0 / v0;
    simulationStopTime = 1500/TransferRequestArrivalRate + nodeMovementTransientTime;

    TransferRequestArrivalRate = 0.1;
    for(int i = 0;i < 10;i++){

        double throughput = calculateThroughput(v0);
        cout << "run " << i << ", throughput " << throughput << endl;
    }
}

void loadDependencyStudy(){
	double v0=0.1;
    nodeMovementTransientTime = 4.0 / v0;


    double arrivalRate[] = {0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.4, 0.5, 0.7, 0.9};
    //loop through the arrival Rates
    for(size_t k=0; k<sizeof(arrivalRate)/sizeof(double); k++) {

    	TransferRequestArrivalRate =  arrivalRate[k];

        simulationStopTime = 1500 /TransferRequestArrivalRate + nodeMovementTransientTime;
        cout << "Arrival Rate: " << TransferRequestArrivalRate << endl;
        for(int i = 0;i < 10;i++){
            calculateThroughput(v0);
        }

        cout << "------------------------------" << endl;
    }


}
// Implement your main method here.
// - Generate the controller, nodes, necessary parameters, RNGs and so on.
// - Initialize the nodes (init() member function)
// - Use the Mersenne Twister random number generator(s)
// - Remember to start the simulation by sending the initial events. 
// - use add_node member function to add nodes to the controller

int main(int argc, char **argv)
 {
	int scenario = 1;
	cout << "Which simulation to run? \n"
			<< "1) movementVelocityDependencyStudy\n"
			<< "2) loadDependencyStudy\n";

	cin >> scenario;
	switch (scenario) {
	case 1:
		movementVelocityDependencyStudy();
		break;
	case 2:
		loadDependencyStudy();
		break;
	default:
		cout << "wrong input! Bye!\n" ;
		break;
	}
	return 0;
}
